using System.Threading;

namespace System
{
    /// <summary>
    /// Implementation of <see cref="IAsyncOperation{TArgs,TResult}">IAsyncOperation</see>.
    /// </summary>
    /// <typeparam name="TArgs"></typeparam>
    /// <typeparam name="TResult"></typeparam>
    public class AsyncOperation<TArgs, TResult> : IAsyncOperation<TArgs, TResult>
    {
        private readonly Func<TArgs, TResult> _task;

        /// <summary>
        /// .ctor
        /// </summary>
        /// <param name="task"><see cref="Func{TArgs, TResult}">Task</see> to be asynchroniously executed.</param>
        public AsyncOperation(Func<TArgs, TResult> task)
        {
            _task = task;
        }

        /// <summary>
        /// Begins async execution.
        /// </summary>
        /// <param name="args">Arguments.</param>
        /// <param name="callback"><see cref="AsyncCallback">Callback</see>.</param>
        /// <param name="state">State.</param>
        /// <returns><see cref="IAsyncResult">IAsyncResult</see> implementation.</returns>
        public IAsyncResult Begin(TArgs args, AsyncCallback callback, object state)
        {
            var ar = new AsyncResult<TResult>(callback, state);

            ThreadPool.QueueUserWorkItem(
                lar =>
                {
                    AsyncResult<TResult> tmp = null;

                    try
                    {
                        tmp = (AsyncResult<TResult>)lar;
                        var res = _task != null ? _task(args) : default(TResult);
                        tmp.SetAsCompleted(res, false);
                    }
                    catch (Exception e)
                    {
                        if (tmp != null)
                        {
                            tmp.SetAsCompleted(e, false);
                        }
                    }
                    
                }, ar);

            return ar;
        }

        /// <summary>
        /// Completes async execution.
        /// </summary>
        /// <param name="asyncResult"><see cref="IAsyncResult">IAsyncResult</see> implementation.</param>
        /// <returns>Result of async execution.</returns>
        public TResult End(IAsyncResult asyncResult)
        {
            var ar = (AsyncResult<TResult>) asyncResult;

            var res = ar.Complete();

            return res;
        }
    }
}